package org.sparkle.scanner;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AnnotationScanner {

	//String baseFilePath = "bin";
	Logger log=LoggerFactory.getLogger(AnnotationScanner.class);

	Map<String, Map<String,Object>> classes;

	public AnnotationScanner() throws IOException {

		for(URL url: getUrlToScan()){
		
			File basePath;
			try {
				basePath = new File(url.toURI());
				if(basePath.isDirectory())
					scanFolder(basePath,basePath);
				else
					scan(basePath);
			} catch (URISyntaxException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			
		}

	}

	public void scanFolder(File file,File baseFolder) throws IOException {
		
		InputStream input=null;
		DataInputStream dstream=null;
		
		for(File f: file.listFiles()){
			if(f.isDirectory()){
				scanFolder(f, baseFolder);
				continue;
			}
			
			if (f.getName().endsWith(".class")) {
	
				String clazz = f.getPath()
						.replaceFirst(baseFolder.getPath(), "")
						.replaceAll("/", ".").replaceFirst(".", "");
				//clazz = clazz.replaceFirst(".class", "");
				
				//System.out.println("Class : " + clazz);
				
				input = new FileInputStream(f);
				dstream = new DataInputStream(new BufferedInputStream(input));
				ClassFile cf=new ClassFile(dstream);
				process(cf);
				dstream.close();
				input.close();
			}
		}
	}

	public Set<URL> getUrlToScan() {
		ClassLoader classLoader = getClass().getClassLoader();
		URLClassLoader uc = null;
		if (classLoader instanceof URLClassLoader) {
			uc = (URLClassLoader) classLoader;
		} else {
			throw new RuntimeException(
					"classLoader is not an instanceof URLClassLoader");
		}
		URL[] urLs = uc.getURLs();
		// just have a look at what you get...
		for (int i = 0; i < urLs.length; i++) {
			log.info("jar " + i + ". " + urLs[i]);
		}

		Map<URL, Object> urls = new HashMap<URL,Object>();
		ClassLoader[] classloaders = { getClass().getClassLoader(),
				Thread.currentThread().getContextClassLoader() };
		for (int i = 0; i < classloaders.length; i++) {
			if (classloaders[i] instanceof URLClassLoader) {				
				for(URL u: ((URLClassLoader)classloaders[i]).getURLs()){
					urls.put(u, null);
				}				
			} else {
				throw new RuntimeException(
						"classLoader is not an instanceof URLClassLoader");
			}
		}
		
		return urls.keySet();
	}

	public void scan(File file) throws IOException {

		JarFile jarFile = new JarFile(file);
		final Enumeration<JarEntry> entries = jarFile.entries();
		InputStream input=null;
		DataInputStream dstream=null;
		while (entries.hasMoreElements()) {
			final JarEntry entry = entries.nextElement();
			
			if(entry.getName().endsWith(".class")){
				input = jarFile.getInputStream(entry);
				dstream = new DataInputStream(new BufferedInputStream(input));
				ClassFile cf=new ClassFile(dstream);
				process(cf);
				dstream.close();
				input.close();
			}			
		}
		jarFile.close();
	}

	@SuppressWarnings("rawtypes")
	private void process(ClassFile cf) throws IOException {
		AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
		if(visible!=null){
			for (javassist.bytecode.annotation.Annotation ann : visible.getAnnotations()){
				//System.out.println("@" + ann.getTypeName());
				
				Map<String,Object> l = getClasses().get(ann.getTypeName());
				if (l == null) {
					l = new HashMap<String,Object>();
					getClasses().put(ann.getTypeName(), l);
				}
				l.put(cf.getName(),null);						
			}
		}
	}

	protected Map<String, Map<String,Object>> getClasses() {
		if (classes == null)
			classes = new HashMap<String, Map<String,Object>>();
		return classes;
	}
	
	public List<Class> getListByAnnotation(Class annotationClass){
		return getListByAnnotation(annotationClass.getName());
	}
	
	public List<Class> getListByAnnotation(String name){
		if(getClasses().get(name)!=null){
			
			List<Class>result=new ArrayList<Class>();
			
			for(String s: getClasses().get(name).keySet()){
				try {
					result.add(getClass().getClassLoader().loadClass(s));
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			return result;
		}
		return null;
	}
 
}
